package correctness;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;

import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.LocsiParser;
import model.editormodel.LogicEditorElement;
import model.editormodel.Output;
import model.simulationmodel.SimulationModel;
import model.simulationmodel.logicelements.LogicSimulationElement;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import controller.commoncontroller.UtilityDialogs;

import view.UtilityConstants;

/**
 * Diese Testklasse fuegt an zufaellig gewaehlten Koordinaten Bauteile ein. Die
 * Typen werden dabei zufaellig aus allen moeglichen Typen ausgewaehlt.
 * Anschliessend werden alle vorhandenen Eingaenge zufaellig mit Ausgaengen
 * verbunden. Nachdem nun eine willkuerliche Schaltung erzeugt wurde, werden
 * verschiedene Methoden aufgerufen. Dabei liegt der Schwerpunkt des Tests darin
 * schwere Programm Fehler aufzudecken die durch eine zufaellige Konstellation
 * entstanden sind. Zu diesem Zweck werden Methoden aufgerufen, aber nicht
 * zwingender weise deren Ergebnis kontrolliert.
 * 
 * @author Team Locsi
 * 
 */
public class RandomTest {

	// maximale Koordinate der X und Y Werte
	// dieser Wert muss im Verhaeltnis der Elemente groß genug gewaehlt werden,
	// damit auch jedes Element sozusagen einen Platz bekommen kann
	private final static int MAXCOORD = 10000;

	// maximales Ausmass eines Bauteils
	private final static int RASTER = 100;

	// maximale Anzahl an Elementen die erzeugt werden sollen
	private final static int MAXELEMENTS = 10;

	// Name des selbstdefinierten Bauteils, dass verwendet werden soll
	// es muss natuerlich sichgestellt sein, dass dieses Bauteil vorhanden ist
	private final static String SDNAME = "random_fuer_JUNIT";

	// Name der Schaltung, der beim abspeichern verwendet wird
	private static String name = "randomTest";

	// Objekte die benoetigt werden
	private static EditorModel eModel;
	private static SimulationModel sModel;
	private static LocsiParser locsiParser;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		eModel = new EditorModel();
		sModel = new SimulationModel(eModel);
		locsiParser = new LocsiParser(eModel);
	}

	@Before
	public void setUp() throws Exception {

	}

	@Test
	public void test() {
		String location = "test" + UtilityConstants.SEPARATOR + "model"
				+ UtilityConstants.SEPARATOR + "editormodel"
				+ UtilityConstants.SEPARATOR + "test_saved_circuits"
				+ UtilityConstants.SEPARATOR + name
				+ UtilityDialogs.XML_EXTENSION;
		LogicType type;
		Coord xCoord;
		Coord yCoord;
		TreeSet<Coord> usedXCoord = new TreeSet<Coord>();
		TreeSet<Coord> usedYCoord = new TreeSet<Coord>();
		List<Input> inputs = new ArrayList<Input>();
		List<Output> outputs = new ArrayList<Output>();

		// erzeugen von Elementen in der gewuenschten Anzahl
		for (int i = 0; i < MAXELEMENTS; ++i) {
			type = generateLogicType();
			// generieren einer x Koordinate, die bisher noch nicht verwendet
			// wurde
			do {
				xCoord = new Coord(generateRandomCoord());
			} while (usedXCoord.contains(xCoord));
			usedXCoord.add(xCoord);

			// generieren einer y Koordinate, die bisher noch nicht verwendet
			// wurde
			do {
				yCoord = new Coord(generateRandomCoord());
			} while (usedYCoord.contains(yCoord));
			usedYCoord.add(yCoord);

			// pruefen ob es sich um ein selbstdefiniertes Bauteil handelt
			// sollte dies der Fall sein, muss der Name des dafuer vorgesehenen
			// Bauteils mit uebergeben werden
			if (type == LogicType.SELFDEFINED) {
				eModel.addLogicElement(type, SDNAME, xCoord.getValue(),
						yCoord.getValue());
			} else {
				eModel.addLogicElement(type, null, xCoord.getValue(),
						yCoord.getValue());
			}

			// alle vorhandenen Inputs und Outputs sollen registriert werden
			for (LogicEditorElement e : eModel.getCircuitModel()
					.getUnmodifableElementsOnEditor().values()) {
				if (LogicType.SELFDEFINED == e.getType()) {
					inputs.addAll(e.getInputs().values());
					outputs.addAll(e.getOutputs().values());
				}
				if (LogicType.ONE_INPUT.contains(e.getType())) {
					inputs.add(e.getInputs().get(Input.STD_A));
				}
				if (LogicType.TWO_INPUTS.contains(e.getType())) {
					if (e.getType() == LogicType.FLIPFLOP) {
						inputs.add(e.getInputs().get(Input.FF_SET));
						inputs.add(e.getInputs().get(Input.FF_RESET));
					} else {
						inputs.add(e.getInputs().get(Input.STD_A));
						inputs.add(e.getInputs().get(Input.STD_B));
					}
				}
				if (LogicType.THREE_INPUTS.contains(e.getType())) {
					inputs.add(e.getInputs().get(Input.SELECT));
					inputs.add(e.getInputs().get(Input.STD_A));
					inputs.add(e.getInputs().get(Input.STD_B));
				}
				if (LogicType.ONE_OUTPUT.contains(e.getType())) {
					outputs.add(e.getOutputs().get(Output.STD_O));
				}
				if (LogicType.TWO_OUTPUTS.contains(e.getType())) {
					outputs.add(e.getOutputs().get(Output.FF_Q));
					outputs.add(e.getOutputs().get(Output.FF_NQ));
				}
			}
		}

		// spontanes erzeugen von Verbindungen
		int iput = 0;
		int oput = 0;
		Input in;
		Output out;

		// Überwiegt die Anzahl der Inputs so müssen einige Outputs mehrfach
		// verwendet werden, anonsten ist es auch möglich, dass manche
		// Outputs nicht verwendet werden
		if (inputs.size() > outputs.size()) {

			// zuerst muessen also die Anzahl der Outputs angeglichen
			// werden, dies geschieht indem diese redundant in der outputs
			// Liste aufgeführt werden
			int diff = inputs.size() - outputs.size();
			int osize = outputs.size();
			for (int k = 0; k < diff; ++k) {
				outputs.add(outputs.get(generateRandomInt(osize)));
			}

		}
		while (!inputs.isEmpty()) {
			iput = generateRandomInt(inputs.size());
			in = inputs.remove(iput);
			oput = generateRandomInt(outputs.size());
			out = outputs.remove(oput);
			eModel.setConnection(out, in);
		}

		// Die Schaltung wird im Standardordner abgespeichert um auftretende
		// Bugs leichter finden zu können, da sich die Konstellation bei jedem
		// Aufruf ändert
		assertTrue(
				"Schaltung konnte nicht gespeichert werden",
				locsiParser.saveCircuit(name, location,
						eModel.getCircuitModel()));

		// es wird ueberprueft ob die gewuenschte Anzahl an Elemente generiert
		// wurde
		assertTrue(
				"Die Anzahl der gewuenschten Elemente stimmt nicht mit der tatsächlichen überein",
				MAXELEMENTS == eModel.getCircuitModel()
						.getUnmodifableElementsOnEditor().size());

		// ueberpruefen ob es sich bei der zufaellig erzeugten Schaltung um eine
		// Komponente handelt
		boolean isComponent = eModel.getCircuitModel().isComponent();

		// handelt es sich um eine Komponente, so kann die Schaltung als eigenes
		// Bauteil exportiert werden bzw. auch simuliert
		if (isComponent) {
			eModel.exportComponent(SDNAME);
			sModel.updateSimulationElements();

			// Berechnungen an allen Setter Elementen durchfuehren
			for (LogicSimulationElement setter : sModel.getSimulationElements()
					.values()) {
				if (LogicType.SETTERS.contains(setter.getLogicEditorElement()
						.getType())) {
					setter.calcLogicValue();
				}
			}
			sModel.calcElementsToCalc();

			// Logiktabelle erzeugen lassen
			sModel.createLogicTable();
		}
		eModel.getCircuitModel().isLogicCircuit();
	}

	/**
	 * Diese Methode waehlt durch einen random Zugriff einen LogicType aus allen
	 * vorhandenen Logiktypen aus
	 * 
	 * @return LogicType - Zufaellig gewaehlter <code>LogicType</code>
	 */
	public LogicType generateLogicType() {
		int maxDiffElements = LogicType.values().length;
		int randomIndex = (int) (Math.random() * maxDiffElements);
		return LogicType.values()[randomIndex];
	}

	/**
	 * Diese Methode waehlt eine Zahl zwischen 0 und leastUpperBound - 1
	 * zufaellig aus
	 * 
	 * @param leastUpperBound
	 *            - <code>int</code> obere Grenze die nicht erreicht wird
	 * @return <code>int</code> - zufaellig gewaehlte Zahl zwischen 0 und
	 *         leastUpperBound - 1
	 */
	public int generateRandomInt(int leastUpperBound) {
		return (int) (Math.random() * (leastUpperBound));
	}

	/**
	 * Diese Methode generierte eine Zufallszahl die kleiner als MAXCOORD
	 * geteilt durch Raster ist. Der Wert des Rasters beeinflusst die "Lucken"
	 * zwischen den Zufallszahlen, denn es soll so weit wie möglich zu keiner
	 * Ueberschneidung kommen. Dabei sollte den Wert des Rasters durch die
	 * maximale Hoehe/Breite der Bauteile bestimmt werden.
	 */
	public double generateRandomCoord() {
		double coord = Math.random() * (MAXCOORD / RASTER);
		return coord * RASTER;
	}

	/**
	 * Dies innere Klasse wird benoetigt um die Koordinaten als Objekt in einer
	 * Tree zu speichern und dadurch eine effizientere Laufzeit zu erhalten. Sie
	 * beinhalte lediglich ein double Attribut und implementiert Comparable,
	 * dabei wird einfach die natuerliche Ordnung von double Zahlen verwendet
	 * 
	 * @author Team Locsi
	 * 
	 */
	public class Coord implements Comparable<Coord> {
		double coord;

		/**
		 * Konstruktor
		 * 
		 * @param coord
		 *            <code>double</code> - zu speichernde Koordinate
		 */
		public Coord(double coord) {
			this.coord = coord;
		}

		@Override
		public int compareTo(Coord o) {
			if (this.coord < o.getValue()) {
				return -1;
			}
			if (this.coord > o.getValue()) {
				return 1;
			}
			return 0;
		}

		/**
		 * Gibt die gespeicherte Koordinate zurueck
		 * 
		 * @return <code>double</double> - Koordinate
		 */
		public double getValue() {
			return this.coord;
		}
	}
}
